package com.zhilei.filter;

import com.google.gson.Gson;
import com.zhilei.base.BaseInfoProperties;
import com.zhilei.grace.result.GraceJSONResult;
import com.zhilei.grace.result.ResponseStatusEnum;
import com.zhilei.untils.IPUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;


/**
 * IP过滤器，过滤IP
 *
 * @author 志磊
 */
@Component
@Slf4j
public class IPLimitFilter extends BaseInfoProperties implements GlobalFilter, Ordered {

    // 使用路径资源匹配器进行精确匹配
    private final AntPathMatcher pathMatcher = new AntPathMatcher();

    @Autowired
    private ExcludeUrlProperties excludeUrlProperties;

    // 黑名单中限制的时间，单位：秒
    @Value("${blackIP.limitTimes}")
    private Integer blackIPLimitTimes;

    // 黑名单中ip连续请求的次数 3
    @Value("${blackIP.continueCounts}")
    private Integer blackIPContinueCounts;

    // 黑名单中ip判断的时间间隔，单位：秒
    @Value("${blackIP.timeInterval}")
    private Integer blackIPTimeInterval;

    /**
     * 实现全局过滤器的接口
     *
     * @param exchange 交换机
     * @param chain    是否放行
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // TODO 1.首先通过交换机获取请求的ip ---> 返回的就是ServerHttpRequest
        String url = exchange.getRequest().getURI().getPath();
        log.debug("Filter url: {}", url);

        // TODO 3. 获取配置文件中的url，直接放行
        List<String> ipLimitUrlList = excludeUrlProperties.getIpLimitUrls();
        if (ipLimitUrlList != null && !ipLimitUrlList.isEmpty()) {
            for (String ipLimitUrl : ipLimitUrlList) {
                // 要是精确匹配的话，需要使用路径规则匹配器
                if (pathMatcher.match(ipLimitUrl, url)) {
                    log.info("IPLimitFilter - 被拦截到：url = {}", url);
                    return doLimit(exchange, chain);
                }
            }
        }

        // TODO 放行
        return chain.filter(exchange);

    }

    /**
     * 封装一个请求到达时校验ip是否存在的方法
     *
     * @param exchange
     * @param chain
     * @return
     */
    public Mono<Void> doLimit(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        String ip = IPUtil.getIP(request);

        // 正常的IP
        final String ipRedisKey = "gateway-ip:" + ip;
        // 被拦截的黑名单，如果存在，表示目前被关小黑屋
        final String ipRedisLimitKey = "gateway-ip-limit:" + ip;

        // TODO 1. 判断ip的次数是不是等于1

        // 设置ip的key +1
        long requestCounts = redis.increment(ipRedisKey, 1);
        // 如果ip的次数等于1，表示第一次请求,正常的key放到redis中
        if (requestCounts == 1) {
            // 设置正常IP，跟ip的过期时间
            redis.expire(ipRedisKey, blackIPTimeInterval);
        }

        // 如果还能取得到请求次数，说明用户连续请求的次数落在20秒内
        // 一旦请求次数超过了连续访问的次数，则需要限制这个ip了
        if (requestCounts >= blackIPContinueCounts) {
            // 限制ip访问一段时间
            redis.set(ipRedisLimitKey, ipRedisLimitKey, blackIPLimitTimes);

            // 终止请求，返回错误
            return renderErrorMsg(exchange, ResponseStatusEnum.SYSTEM_ERROR_BLACK_IP);
        }

        // TODO 2. 获取小黑屋的ip的剩余时间
        long limitLeftTime = redis.ttl(ipRedisLimitKey);
        if (limitLeftTime > 0) {
            // TODO 3. 如果剩余时间大于0，表示还在时间内，不许与访问
            return renderErrorMsg(exchange, ResponseStatusEnum.SYSTEM_ERROR_BLACK_IP);
        }

        // TODO 4. 返回
        return chain.filter(exchange);
    }


    /**
     * 1. 自定义返回的包装类，返回一个json的格式
     * 网关无法进行全局异常处理，自定义异常无法捕获网关层的异常
     *
     * @param exchange   交换机
     * @param statusEnum 异常的枚举类
     * @return
     */
    private Mono<Void> renderErrorMsg(ServerWebExchange exchange,
                                      ResponseStatusEnum statusEnum) {

        // TODO 1. 获得返回的response
        ServerHttpResponse response = exchange.getResponse();

        // TODO 2. 构建返回的json
        GraceJSONResult JSONResult = GraceJSONResult.exception(statusEnum);

        // TODO 3. 构建返回的code值为500
        response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);

        // TODO 4. 构建返回的Content-Type,判断是否存在，不存在就新增一个
        if (!response.getHeaders().containsKey("Content-Type")) {
            response.getHeaders().set("Content-Type", "application/json;charset=UTF-8");
        }

        // TODO 5. 转换成Mono<Void> 类型返回
        DataBuffer dataBuffer = response.bufferFactory().wrap(new Gson().toJson(JSONResult).getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(dataBuffer));

    }


    /**
     * 重新构建request
     *
     * @param exchange
     * @param headerKey
     * @param headerValue
     * @return
     */
    public ServerWebExchange getExchange(ServerWebExchange exchange, String headerKey, String headerValue) {
        // TODO 1.通过exchange获得request
        ServerHttpRequest request = exchange.getRequest().mutate().header(headerKey, headerValue).build();
        // TODO 2. 构建一个新的exchange
        ServerWebExchange newExchange = exchange.mutate().request(request).build();
        return newExchange;
    }

    @Override
    public int getOrder() {
        log.error("IPLimitFilter 执行顺序为 1，开始执行");
        return 1;
    }
}












